package sy6_Behavioral_Pattern.visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/*
 * 角色：
 *  角色1：对象结构类ObjectStructure，存储不同类型的元素（对象）。
 * 	角色2：元素类Element，定义了抽象方法accept(Visitor)，用于接受访问者的访问。
 * 	角色3：具体元素类ConcreteElement，重写抽象方法accept(Visitor)。
 * 	角色4：访问者类Visitor，为ObjectStructure定义的每种具体元素类型定义了相应的抽象访问方法，如visitConcreteElementA(ConcreteElementA)等。
 * 	角色5：具体访问者类ConcreteVisitor，重写Visitor定义的抽象方法。
 * 
 * 要点：
 * 	1. 同一访问者可以用不同的方式访问不同的元素，同一元素可以接受不同的访问者以不同的方式访问。
 * 	2. 增加新的具体访问类，不需要修改现有系统，就能实现对这些不同类型的元素增加新的操作。即系统具有良好的扩展性。
 * 	3. Visitor和Element相互依赖，Client分别关联Visitor和ObjectStructure，类ObjectStructure是元素的集合
 * 
 * 访问者模式示例：
 * 评委对参赛歌手的投票（打分）
 * 两个相互依赖的抽象类：Visitor是对评委的抽象、Element是对歌手的抽象
 */

abstract class Element{  //角色：元素Element，表示（抽象）歌手
	public abstract void accept(Visitor visitor); //抽象方法（投票）
}

abstract class Visitor{   //角色：访问者Visitor，表示（抽象）评委
	//评委给不同种类的歌手投票
	public abstract void visitConcreteElementA(ConcreteElementA ceA);
	public abstract void visitConcreteElementB(ConcreteElementB ceB);
}

class ObjectStructure{  //角色：对象结构ObjectStructure，是元素的集合
	
	List<Element> elements=new ArrayList<Element>();
	
	public void attach(Element element) {  //增加
		elements.add(element);
	}
	public void detach(Element element) {  //删除
		elements.remove(element);
	}
	
	public void displayAll(Visitor visitor) {  //每位评委要给所有歌手进行评价
		for(Element element:elements) {  //遍历所有歌手
			element.accept(visitor);  //接受评委的投票结果
		}
	}
}

class ConcreteElementA extends Element{  //角色：具体元素ConcreteElementA，表示具体歌手
	@Override
	public void accept(Visitor visitor) {  //重写抽象方法
		visitor.visitConcreteElementA(this);  //调用评委对歌手的打分方法
	}
	public void operationA() {  //普通方法
		//System.out.println("歌手A最终得分："+(Math.round(85+Math.random()*(100-85))));  //产生85-100之间的随机整数
		System.out.println("歌手A最终得分："+(85+new Random().nextInt(15)));
	}
}

class ConcreteElementB extends Element{   //具体歌手
	
	@Override
	public void accept(Visitor visitor) {   //重写抽象方法
		visitor.visitConcreteElementB(this);  //调用评委对歌手的打分方法
	}
	
	public void operationB() {   //普通方法
		System.out.println("歌手B最终得分："+(85+new Random().nextInt(15)));
		//System.out.println("歌手B最终得分："+(Math.round(85+Math.random()*(100-85))));
	}
}

class ConcreteVisitorA extends Visitor{  //角色：具体访问者ConcreteVisitor
	@Override
	public void visitConcreteElementA(ConcreteElementA ceA) {
		System.out.println("评委"+this.hashCode()+"对歌手A评分");
		ceA.operationA();  //调用歌手的普通方法
	}
	@Override
	public void visitConcreteElementB(ConcreteElementB ceB) {
		System.out.println("评委"+this.hashCode()+"对歌手B评分");
		ceB.operationB();  //调用歌手的普通方法
	}
}

class ConcreteVisitorB extends Visitor{  //具体访问者
	
	@Override
	public void visitConcreteElementA(ConcreteElementA ceA) {
		System.out.println("评委"+this.hashCode()+"对歌手A评分");
		ceA.operationA();  //调用歌手的普通方法
	}
	
	@Override
	public void visitConcreteElementB(ConcreteElementB ceB) {
		System.out.println("评委"+this.hashCode()+"对歌手B评分");
		ceB.operationB();  //调用歌手的普通方法
	}
}

class ConcreteVisitorC extends Visitor{  //新增评委
	
	@Override
	public void visitConcreteElementA(ConcreteElementA ceA) {
		System.out.println("新增评委"+this.hashCode()+"对歌手A评分");
		ceA.operationA();  //调用歌手的普通方法
	}
	
	@Override
	public void visitConcreteElementB(ConcreteElementB ceB) {
		System.out.println("新增评委"+this.hashCode()+"对歌手B的评分");
		//ceB.opeationB();  //调用歌手的普通方法
		System.out.println("歌手B的得分：待定");
	}
}


public class Client {  //客户端
	
	public static void main(String[] args) {
		
		//创建ObjectStructure对象，体现Client对ObjectStructure的关联
		ObjectStructure objectStructure=new ObjectStructure();
		//ObjectStructure关联Element
		Element elementA=new ConcreteElementA();  //创建歌手
		Element elementB=new ConcreteElementB();
		objectStructure.attach(elementA);  //添加歌手
		objectStructure.attach(elementB);
		
		Visitor visitor=new ConcreteVisitorA();  //Client关联Visitor
		objectStructure.displayAll(visitor);  //显示具体访问者对所有歌手的访问结果
		System.out.println("========================");
		visitor=new ConcreteVisitorB();
		objectStructure.displayAll(visitor);  
		
		System.out.println("\n===对于增加新的访问者、出现新的访问结果，这种扩展不必修改其它类===");
		visitor=new ConcreteVisitorC();
		objectStructure.displayAll(visitor);  //此时，需要取消对类ConcreteVisitorC的注释
	}
}
